home *** CD-ROM | disk | FTP | other *** search
- /* Virtual File System: GNU Tar file system.
- Copyright (C) 1995 The Free Software Foundation
-
- Written by: 1995 Jakub Jelinek
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
-
- #include <config.h>
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <signal.h>
- #include <sys/wait.h>
- #include <errno.h>
- #include <time.h>
- #include "../src/fs.h"
- #include "../src/util.h"
- #include "../src/mem.h"
- #include "../src/mad.h"
- #include "vfs.h"
- #include "tar.h"
- #include "names.h"
-
- /* The limit for a compressed tar file to be loaded in core */
- int tar_gzipped_memlimit = 1*1024*1024;
-
- /* used to rotate the dash */
- int dash_number = 0;
-
- /* In order not to include all dialoging stuff, I'm not including dialog.h
- just for this function */
- extern void *message (int error, char *header, char *text,...);
-
- #define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
- /*
- * Quick and dirty octal conversion.
- *
- * Result is -1 if the field is invalid (all blank, or nonoctal).
- */
- long from_oct (int digs, char *where)
- {
- register long value;
-
- while (isspace (*where)) { /* Skip spaces */
- where++;
- if (--digs <= 0)
- return -1; /* All blank field */
- }
- value = 0;
- while (digs > 0 && isodigit (*where)) { /* Scan till nonoctal */
- value = (value << 3) | (*where++ - '0');
- --digs;
- }
-
- if (digs > 0 && *where && !isspace (*where))
- return -1; /* Ended on non-space/nul */
-
- return value;
- }
-
- static struct tarfs_archive *first_archive = NULL;
- static int tarerrno = 0;
- static struct stat hstat; /* Stat struct corresponding */
- static char *current_file_name, *current_link_name;
- static struct tarfs_entry *tarfs_find_entry (struct tarfs_entry *dir, char *name, int make_dirs);
-
- void tarfs_fill_names (void (*func)(char *))
- {
- struct tarfs_archive *a = first_archive;
- char *name;
-
- while (a){
- name = copy_strings ("tar:", a->name, "/",
- a->current_dir->name, 0);
- (*func)(name);
- free (name);
- a = a->next;
- }
- }
-
- static void make_dot_doubledot (struct tarfs_entry *ent)
- {
- struct tarfs_entry *entry = (struct tarfs_entry *)
- xmalloc (sizeof (struct tarfs_entry), "Tar: tarfs_entry");
- struct tarfs_entry *parentry = ent->dir;
- struct tarfs_inode *inode = ent->inode, *parent;
-
- parent = (parentry != NULL) ? parentry->inode : NULL;
- entry->name = strdup (".");
- entry->has_changed = 0;
- entry->header_offset = -1;
- entry->header_size = 0;
- entry->extended_offset = -1;
- entry->extended_size = 0;
- entry->inode = inode;
- entry->dir = ent;
- inode->first_in_subdir = entry;
- inode->last_in_subdir = entry;
- inode->nlink++;
- entry->next_in_dir = (struct tarfs_entry *)
- xmalloc (sizeof (struct tarfs_entry), "Tar: tarfs_entry");
- entry=entry->next_in_dir;
- entry->name = strdup ("..");
- entry->has_changed = 0;
- entry->header_offset = -1;
- entry->header_size = 0;
- entry->extended_offset = -1;
- entry->extended_size = 0;
- inode->last_in_subdir = entry;
- entry->next_in_dir = NULL;
- if (parent != NULL) {
- entry->inode = parent;
- entry->dir = parentry;
- parent->nlink++;
- } else {
- entry->inode = inode;
- entry->dir = ent;
- inode->nlink++;
- }
- }
-
- static struct tarfs_entry *generate_entry (struct tarfs_archive *archive,
- char *name, struct tarfs_entry *parentry, mode_t mode)
- {
- mode_t myumask;
- struct tarfs_inode *inode, *parent;
- struct tarfs_entry *entry;
-
- parent = (parentry != NULL) ? parentry->inode : NULL;
- entry = (struct tarfs_entry *)
- xmalloc (sizeof (struct tarfs_entry), "Tar: tarfs_entry");
-
- entry->name = strdup (name);
- entry->has_changed = 0;
- entry->header_offset = -1;
- entry->header_size = 0;
- entry->extended_offset = -1;
- entry->extended_size = 0;
- entry->next_in_dir = NULL;
- entry->dir = parentry;
- if (parent != NULL) {
- parent->last_in_subdir->next_in_dir = entry;
- parent->last_in_subdir = entry;
- }
- inode = (struct tarfs_inode *)
- xmalloc (sizeof (struct tarfs_inode), "Tar: tarfs_inode");
- entry->inode = inode;
- inode->local_filename = NULL;
- inode->has_changed = 0;
- inode->is_open = 0;
- inode->linkname = 0;
- inode->inode = (archive->__inode_counter)++;
- inode->dev = archive->rdev;
- inode->archive = archive;
- inode->data_offset = -1;
- myumask = umask (022);
- umask (myumask);
- inode->mode = mode & ~myumask;
- mode = inode->mode;
- inode->rdev = 0;
- inode->uid = getuid ();
- inode->gid = getgid ();
- inode->std = 1;
- inode->size = 0;
- inode->mtime = time (NULL);
- inode->atime = inode->mtime;
- inode->ctime = inode->mtime;
- inode->nlink = 1;
- if (S_ISDIR (mode)) {
- inode->linkflag = LF_DIR;
- make_dot_doubledot (entry);
- } else if (S_ISLNK (mode)) {
- inode->linkflag = LF_SYMLINK;
- } else if (S_ISCHR (mode)) {
- inode->linkflag = LF_CHR;
- } else if (S_ISBLK (mode)) {
- inode->linkflag = LF_BLK;
- } else if (S_ISFIFO (mode) || S_ISSOCK (mode)) {
- inode->linkflag = LF_FIFO;
- } else {
- inode->linkflag = LF_NORMAL;
- }
- return entry;
- }
-
- static void free_entries (struct tarfs_entry *entry)
- {
- return;
- }
-
- static void free_archive (struct tarfs_archive *archive)
- {
- long l;
-
- if (archive->is_gzipped == targz_growing) {
- free (archive->block_first);
- if (archive->block_ptr != NULL) {
- for (l = 0; l < archive->count_blocks; l++)
- free (archive->block_ptr [l]);
- free (archive->block_ptr);
- }
- } else {
- if (archive->is_gzipped == tar_uncompressed_local) {
- mc_unlink (archive->tmpname);
- free (archive->tmpname);
- }
- if (archive->fd != -1)
- mc_close(archive->fd);
- }
- free_entries (archive->root_entry);
-
- free (archive->name);
- free (archive);
- }
-
- static INLINE int gzip_limit_ok (int size)
- {
- return (size <= tar_gzipped_memlimit || tar_gzipped_memlimit < 0);
- }
-
- /* So we have to decompress it...
- * It is not that easy, because we would like to handle all the files
- * from all the vfs's. So, we do this like this:
- * we run a pipe:
- * for (;;) mc_read | gzip -cdf | store into growing buffer
- *
- * Returns: 0 on failure
- */
- static INLINE int load_compressed_tar (struct tarfs_archive *current_archive,
- int size, int fd)
- {
- int pipehandle, i;
- long l, l2;
- union record *ur;
- pid_t p;
-
- l2 = 0;
- current_archive->is_gzipped = targz_growing;
- size = (size + RECORDSIZE - 1) / RECORDSIZE * RECORDSIZE;
- current_archive->block_first =
- (union record *) xmalloc (size, "Tar.gz growing buffers");
- current_archive->count_first = size / RECORDSIZE;
- current_archive->count_blocks = 0;
- current_archive->block_ptr = NULL;
- current_archive->root_entry = NULL;
-
- pipehandle = mc_doublepopen (fd, -1, &p, "gzip", "gzip", "-cdf", NULL);
- if (pipehandle == -1)
- {
- free (current_archive->block_first);
- free (current_archive);
- mc_close (fd);
- return 0;
- }
- ur = current_archive->block_first;
- l = 0;
- while ((i = read (pipehandle, (char *) ur, RECORDSIZE)) == RECORDSIZE) {
- l++;
- if (l >= current_archive->count_first) {
- l2 = l - current_archive->count_first;
- if (l2 % TAR_GROWING_CHUNK_SIZE)
- ur++;
- else {
- union record **tmp = (union record **)
- xmalloc ((++current_archive->count_blocks) *
- sizeof (union record **), "Tar: Growing buffers");
-
- if (current_archive->block_ptr != NULL) {
- bcopy (current_archive->block_ptr, tmp,
- (current_archive->count_blocks - 1) *
- sizeof (union record **));
- free (current_archive->block_ptr);
- }
- current_archive->block_ptr = tmp;
- ur = (union record *)
- xmalloc (TAR_GROWING_CHUNK_SIZE * RECORDSIZE,
- "Tar: Growing buffers");
- current_archive->block_ptr [current_archive->count_blocks - 1] = ur;
- }
- } else
- ur++;
- if ((dash_number++ % 64) == 0)
- rotate_dash ();
- }
- i = mc_doublepclose (pipehandle, p);
- mc_close (fd);
- if (i == -1) {
- free_archive (current_archive);
- return 0;
- }
- current_archive->current_record = current_archive->block_first;
- return 1;
- }
-
- /* Returns a file handle of the opened local tar file or -1 on error */
- static INLINE int uncompress_tar_file (struct tarfs_archive *current_archive,
- int size, int fd)
- {
- FILE *f;
- char *command;
- int i, result;
- int dash_number = 0;
- char buffer [8192]; /* Changed to 8K: better transfer size */
-
- current_archive->is_gzipped = tar_uncompressed_local;
- current_archive->tmpname = strdup (tmpnam (NULL));
-
- /* Some security is sometimes neccessary :) */
- command = copy_strings ("touch ", current_archive->tmpname,
- " ; chmod 0600 ", current_archive->tmpname,
- " ; gzip -cdf 2>/dev/null >", current_archive->tmpname, NULL);
-
- if ((f = popen (command, "w")) == NULL) {
- mc_close (fd);
- free_archive (current_archive);
- free (command);
- return -1;
- }
- free (command);
-
- while ((i = mc_read (fd, buffer, sizeof (buffer))) > 0){
- if ((dash_number++ % 64) == 0)
- rotate_dash ();
- fwrite (buffer, 1, i, f);
- }
-
- pclose (f);
- mc_close (fd);
- result = mc_open (current_archive->tmpname, O_RDONLY);
- if (result == -1){
- free_archive (current_archive);
- return -1;
- }
- return result;
- }
-
- /* Returns fd of the open tar file */
- static int open_tar_archive (char *name, struct tarfs_archive **pparc)
- {
- static dev_t __tar_no = 0;
- int result;
- long size;
- mode_t mode;
- struct tarfs_archive *current_archive;
- static struct tarfs_entry *root_entry;
-
- result = mc_open (name, O_RDONLY);
- if (result == -1)
- return -1;
-
- current_archive = (struct tarfs_archive *)
- xmalloc (sizeof (struct tarfs_archive), "Tar archive");
- current_archive->current_tar_position = 0;
- current_archive->name = strdup (name);
- current_archive->__inode_counter = 0;
- mc_stat (name, &(current_archive->tarstat));
- current_archive->rdev = __tar_no++;
- current_archive->next = first_archive;
- current_archive->fd_usage = 0;
- current_archive->fd = -1;
- size = is_gunzipable (result);
- mc_lseek (result, 0, SEEK_SET);
-
- /* Find out the method to handle this tar file */
- if (size > 0 && gzip_limit_ok (size)) {
- if (load_compressed_tar (current_archive, size, result) == 0)
- return -1;
- result = 0;
- } else if (size > 0) {
- result = uncompress_tar_file (current_archive, size, result);
- if (result == -1)
- return -1;
- } else {
- current_archive->is_gzipped = tar_normal;
- }
-
- current_archive->fd = result;
- first_archive = current_archive;
- mode = current_archive->tarstat.st_mode & 07777;
- if (mode & 0400)
- mode |= 0100;
- if (mode & 0040)
- mode |= 0010;
- if (mode & 0004)
- mode |= 0001;
- mode |= S_IFDIR;
- root_entry = generate_entry (current_archive, "/", NULL, mode);
- root_entry->inode->uid = current_archive->tarstat.st_uid;
- root_entry->inode->gid = current_archive->tarstat.st_gid;
- root_entry->inode->atime = current_archive->tarstat.st_atime;
- root_entry->inode->ctime = current_archive->tarstat.st_ctime;
- root_entry->inode->mtime = current_archive->tarstat.st_mtime;
- current_archive->root_entry = root_entry;
- current_archive->current_dir = root_entry;
-
- *pparc = current_archive;
-
- return result;
- }
-
- static int get_current_position (struct tarfs_archive *archive, int tard)
- {
- return archive->current_tar_position;
- }
-
- static union record *find_current_record (struct tarfs_archive *archive, long pos)
- {
- long l, l2;
- static union record *ur;
-
- l = pos / RECORDSIZE;
- if (l >= archive->count_first) {
- l2 = l - archive->count_first;
- ur = archive->block_ptr [l2 / TAR_GROWING_CHUNK_SIZE]
- + (l2 % TAR_GROWING_CHUNK_SIZE);
- } else
- ur = archive->block_first + l;
- return ur;
- }
-
- static union record rec_buf;
-
- static union record *get_next_record (struct tarfs_archive *archive, int tard)
- {
- if (archive->is_gzipped == targz_growing) {
- bcopy (archive->current_record, rec_buf.charptr, RECORDSIZE);
- archive->current_record = find_current_record (archive, archive->current_tar_position + RECORDSIZE);
- } else if (mc_read (tard, rec_buf.charptr, RECORDSIZE) != RECORDSIZE)
- return NULL; /* An error has occurred */
- archive->current_tar_position += RECORDSIZE;
- return &rec_buf;
- }
-
- static void skip_n_records (struct tarfs_archive *archive, int tard, int n)
- {
- if (archive->is_gzipped == targz_growing)
- archive->current_record = find_current_record (archive, archive->current_tar_position + n * RECORDSIZE);
- else
- mc_lseek (tard, n * RECORDSIZE, SEEK_CUR);
- archive->current_tar_position += n * RECORDSIZE;
- }
-
- /*
- * Return 1 for success, 0 if the checksum is bad, EOF on eof,
- * 2 for a record full of zeros (EOF marker).
- *
- */
- static int read_header (struct tarfs_archive *archive, int tard)
- {
- register int i;
- register long sum, signed_sum, recsum;
- register char *p;
- register union record *header;
- char **longp;
- char *bp, *data;
- int size, written;
- static char *next_long_name = NULL, *next_long_link = NULL;
- long header_position = get_current_position (archive, tard);
-
- recurse:
-
- header = get_next_record (archive, tard);
- if (NULL == header)
- return EOF;
-
- recsum = from_oct (8, header->header.chksum);
-
- sum = 0; signed_sum = 0;
- p = header->charptr;
- for (i = sizeof (*header); --i >= 0;) {
- /*
- * We can't use unsigned char here because of old compilers,
- * e.g. V7.
- */
- signed_sum += *p;
- sum += 0xFF & *p++;
- }
-
- /* Adjust checksum to count the "chksum" field as blanks. */
- for (i = sizeof (header->header.chksum); --i >= 0;) {
- sum -= 0xFF & header->header.chksum[i];
- signed_sum -= (char) header->header.chksum[i];
- }
- sum += ' ' * sizeof header->header.chksum;
- signed_sum += ' ' * sizeof header->header.chksum;
-
- if (sum == 8 * ' ') {
- /*
- * This is a zeroed record...whole record is 0's except
- * for the 8 blanks we faked for the checksum field.
- */
- return 2;
- }
- if (sum != recsum && signed_sum != recsum)
- return 0;
-
- /*
- * Good record. Decode file size and return.
- */
- if (header->header.linkflag == LF_LINK || header->header.linkflag == LF_DIR)
- hstat.st_size = 0; /* Links 0 size on tape */
- else
- hstat.st_size = from_oct (1 + 12, header->header.size);
-
- header->header.arch_name[NAMSIZ - 1] = '\0';
- if (header->header.linkflag == LF_LONGNAME
- || header->header.linkflag == LF_LONGLINK) {
- longp = ((header->header.linkflag == LF_LONGNAME)
- ? &next_long_name
- : &next_long_link);
-
- if (*longp)
- free (*longp);
- bp = *longp = (char *) xmalloc (hstat.st_size, "Tar: Long name");
-
- for (size = hstat.st_size;
- size > 0;
- size -= written) {
- data = get_next_record (archive, tard)->charptr;
- if (data == NULL) {
- message (1, " Error ", "Unexpected EOF on archive file");
- return 0;
- }
- written = RECORDSIZE;
- if (written > size)
- written = size;
-
- bcopy (data, bp, written);
- bp += written;
- }
- bp [hstat.st_size - 1] = 0; /* just to make sure */
- goto recurse;
- } else {
- struct tarfs_entry *entry, *pent;
- struct tarfs_inode *inode;
- long data_position;
- char *p, *q;
- int len;
- int isdir = 0;
-
- current_file_name = (next_long_name
- ? next_long_name
- : strdup (header->header.arch_name));
- len = strlen (current_file_name);
- if (current_file_name[len - 1] == '/') {
- current_file_name[len - 1] = 0;
- isdir = 1;
- }
-
- current_link_name = (next_long_link
- ? next_long_link
- : strdup (header->header.arch_linkname));
- len = strlen (current_link_name);
- if (current_link_name [len - 1] == '/')
- current_link_name[len - 1] = 0;
-
- next_long_link = next_long_name = NULL;
-
- data_position = get_current_position (archive, tard);
-
- p = strrchr (current_file_name, '/');
- if (p == NULL) {
- p = current_file_name;
- q = current_file_name + strlen (current_file_name); /* "" */
- } else {
- *(p++) = 0;
- q = current_file_name;
- }
-
- pent = tarfs_find_entry (archive->root_entry, q, 1);
- if (pent == NULL) {
- message (1, " Error ", "Inconsistent tar archive");
- }
-
- entry = (struct tarfs_entry *) xmalloc (sizeof (struct tarfs_entry), "Tar: tarfs_entry");
- entry->name = strdup (p);
- entry->has_changed = 0;
- entry->header_offset = header_position;
- entry->header_size = data_position - header_position;
- entry->extended_offset = -1;
- entry->extended_size = 0;
- entry->next_in_dir = NULL;
- entry->dir = pent;
- if (pent != NULL) {
- pent->inode->last_in_subdir->next_in_dir = entry;
- pent->inode->last_in_subdir = entry;
- }
- free (current_file_name);
-
- if (header->header.linkflag == LF_LINK) {
- pent = tarfs_find_entry (archive->root_entry, current_link_name, 0);
- if (pent == NULL) {
- message (1, " Error ", "Inconsistent tar archive");
- } else {
- entry->inode = pent->inode;
- pent->inode->nlink++;
- free (current_link_name);
- if (header->header.isextended) {
- entry->extended_offset = data_position;
- while (get_next_record (archive, tard)->ext_hdr.isextended);
- data_position = get_current_position (archive, tard);
- entry->extended_size = data_position - entry->extended_offset;
- }
- return 1;
- }
- }
- inode = (struct tarfs_inode *) xmalloc (sizeof (struct tarfs_inode), "Tar: tarfs_inode");
- entry->inode = inode;
- inode->local_filename = NULL;
- inode->has_changed = 0;
- inode->is_open = 0;
- inode->inode = (archive->__inode_counter)++;
- inode->nlink = 1;
- inode->dev = archive->rdev;
- inode->archive = archive;
- inode->data_offset = data_position;
- inode->mode = from_oct (8, header->header.mode);
- inode->rdev = 0;
- if (!strcmp (header->header.magic, TMAGIC)) {
- inode->uid = *header->header.uname ? finduid (header->header.uname) :
- from_oct (8, header->header.uid);
- inode->gid = *header->header.gname ? findgid (header->header.gname) :
- from_oct (8, header->header.gid);
- switch (header->header.linkflag) {
- case LF_BLK:
- case LF_CHR:
- inode->rdev = (from_oct (8, header->header.devmajor) << 8) |
- from_oct (8, header->header.devminor);
- }
- inode->std = 1;
- } else { /* Old Unix tar */
- inode->uid = from_oct (8, header->header.uid);
- inode->gid = from_oct (8, header->header.gid);
- inode->std = 0;
- }
- inode->size = hstat.st_size;
- inode->mtime = from_oct (1 + 12, header->header.mtime);
- inode->atime = from_oct (1 + 12, header->header.atime);
- inode->ctime = from_oct (1 + 12, header->header.ctime);
- inode->linkflag = header->header.linkflag;
- if (*current_link_name) {
- inode->linkname = current_link_name;
- } else {
- free (current_link_name);
- inode->linkname = NULL;
- }
- if (inode->linkflag == LF_DIR || isdir) {
- inode->mode |= S_IFDIR;
- make_dot_doubledot (entry);
- }
- if (header->header.isextended) {
- entry->extended_offset = data_position;
- while (get_next_record (archive, tard)->ext_hdr.isextended);
- inode->data_offset = get_current_position (archive, tard);
- entry->extended_size = inode->data_offset - entry->extended_offset;
- }
- return 1;
- }
- }
-
- /*
- * Main loop for reading an archive.
- * Returns 0 on success, -1 on error.
- */
- int read_tar_archive (char *name, struct tarfs_archive **pparc)
- {
- int status = 3; /* Initial status at start of archive */
- int prev_status;
- int tard;
- struct tarfs_archive *archive;
-
- if ((tard = open_tar_archive (name, &archive)) == -1) { /* Open for reading */
- message (1, " Error ", "Couldn't open tar archive\n%s", name);
- return -1;
- }
-
- for (;;) {
- prev_status = status;
- status = read_header (archive, tard);
- switch (status) {
-
- case 1: /* Valid header */
- skip_n_records (archive, tard, (hstat.st_size + RECORDSIZE - 1) / RECORDSIZE);
- continue;
- /*
- * If the previous header was good, tell them
- * that we are skipping bad ones.
- */
- case 0: /* Invalid header */
- switch (prev_status) {
- case 3: /* Error on first record */
- message (1, " Error ", "Hmm,...\n%s\ndoesn't look like a tar archive.", name);
- /* FALL THRU */
- case 2: /* Error after record of zeroes */
- case 1: /* Error after header rec */
- #if 0
- message (0, " Warning ", "Skipping to next file header...");
- #endif
- case 0: /* Error after error */
- return -1;
- }
-
- case 2: /* Record of zeroes */
- status = prev_status; /* If error after 0's */
- /* FALL THRU */
- case EOF: /* End of archive */
- break;
- }
- break;
- };
-
- *pparc = archive;
- return 0;
- }
-
- char *tarfs_analysis (char *name, char **archive, int is_dir)
- {
- static struct {
- int len; /* strlen (ext) */
- char *ext;
- } tarext[] = {{4, ".tar"},
- {4, ".tgz"},
- {7, ".tar.gz"},
- {4, ".taz"},
- {4, ".tpz"},
- {6, ".tar.z"},
- {6, ".tar.Z"} };
- char *p, *local;
- unsigned int i;
- char *archive_name = NULL;
-
- /* | this is len of "tar:" plus some minimum
- * v space needed for the extension */
- for (p = name + strlen (name); p >= name + 8; p--)
- if (*p == '/' || (is_dir && !*p))
- for (i = 0; i < sizeof (tarext) / sizeof (tarext [0]); i++)
- if (!strncmp (p - tarext [i].len, tarext [i].ext, tarext [i].len)) {
- char c = *p;
-
- *p = 0;
- archive_name = vfs_canon (name + 4);
- *archive = archive_name;
- *p = c;
- local = strdup (p);
- return local;
- }
- tarerrno = ENOENT;
- return NULL;
- }
-
- /* Returns allocated path inside the archive or NULL */
- static char *tarfs_get_path (char *inname, struct tarfs_archive **archive, int is_dir,
- int do_not_open)
- {
- char *local, *archive_name;
- int result = -1;
- struct tarfs_archive *parc;
- struct vfs_stamping *parent;
- vfs *v;
-
- local = tarfs_analysis (inname, &archive_name, is_dir);
- if (local == NULL) {
- tarerrno = ENOENT;
- return NULL;
- }
- for (parc = first_archive; parc != NULL; parc = parc->next)
- if (!strcmp (parc->name, archive_name)) {
- vfs_stamp (&tarfs_vfs_ops, (vfsid) parc);
- goto return_success;
- }
- if (do_not_open)
- result = -1;
- else
- result = read_tar_archive (archive_name, &parc);
- if (result == -1) {
- tarerrno = EIO;
- free(local);
- free(archive_name);
- return NULL;
- }
- v = vfs_type (archive_name);
- if (v == &local_vfs_ops) {
- parent = NULL;
- } else {
- parent = xmalloc (sizeof (struct vfs_stamping), "vfs stamping");
- parent->v = v;
- parent->id = (*v->getid) (archive_name, &(parent->parent));
- }
- vfs_add_noncurrent_stamps (&tarfs_vfs_ops, (vfsid) parc, parent);
- return_success:
- *archive = parc;
- free (archive_name);
- return local;
- }
-
- struct tarfs_loop_protect {
- struct tarfs_entry *entry;
- struct tarfs_loop_protect *next;
- };
- static int errloop;
- static int notadir;
-
- static struct tarfs_entry *
- __tarfs_find_entry (struct tarfs_entry *dir, char *name,
- struct tarfs_loop_protect *list, int make_dirs);
-
- static struct tarfs_entry *
- __tarfs_resolve_symlinks (struct tarfs_entry *entry,
- struct tarfs_loop_protect *list)
- {
- struct tarfs_entry *pent;
- struct tarfs_loop_protect *looping;
-
- if (!S_ISLNK (entry->inode->mode))
- return entry;
- for (looping = list; looping != NULL; looping = looping->next)
- if (entry == looping->entry) { /* Here we protect us against symlink looping */
- errloop = 1;
- return NULL;
- }
- looping = (struct tarfs_loop_protect *)
- xmalloc (sizeof (struct tarfs_loop_protect),
- "Tar: symlink looping protection");
- looping->entry = entry;
- looping->next = list;
- pent = __tarfs_find_entry (entry->dir, entry->inode->linkname, looping, 0);
- free (looping);
- if (pent == NULL)
- tarerrno = ENOENT;
- return pent;
- }
-
- static struct tarfs_entry *tarfs_resolve_symlinks (struct tarfs_entry *entry)
- {
- struct tarfs_entry *res;
-
- errloop = 0;
- notadir = 0;
- res = __tarfs_resolve_symlinks (entry, NULL);
- if (res == NULL) {
- if (errloop)
- tarerrno = ELOOP;
- else if (notadir)
- tarerrno = ENOTDIR;
- }
- return res;
- }
-
- static struct tarfs_entry*
- __tarfs_find_entry (struct tarfs_entry *dir, char *name,
- struct tarfs_loop_protect *list, int make_dirs)
- {
- struct tarfs_entry *pent, *pdir;
- char *p, *q, *name_end;
- char c;
-
- if (*name == '/') { /* Handle absolute paths */
- name++;
- dir = dir->inode->archive->root_entry;
- }
-
- pent = dir;
- p = name;
- name_end = name + strlen (name);
- q = strchr (p, '/');
- c = '/';
- if (!q)
- q = strchr (p, 0);
-
- for (; pent != NULL && c && *p; ){
- c = *q;
- *q = 0;
-
- if (strcmp (p, ".")){
- if (!strcmp (p, ".."))
- pent = pent->dir;
- else {
- if ((pent = __tarfs_resolve_symlinks (pent, list))==NULL){
- *q = c;
- return NULL;
- }
- if (c == '/' && !S_ISDIR (pent->inode->mode)){
- *q = c;
- notadir = 1;
- return NULL;
- }
- pdir = pent;
- for (pent = pent->inode->first_in_subdir; pent; pent = pent->next_in_dir)
- /* Hack: I keep the original semanthic unless
- q+1 would break in the strchr */
- if (!strcmp (pent->name, p)){
- if (q + 1 > name_end){
- *q = c;
- notadir = !S_ISDIR (pent->inode->mode);
- return pent;
- }
- break;
- }
-
- /* When we load archive, we create automagically
- * non-existant directories
- */
- if (pent == NULL && make_dirs) {
- pent = generate_entry (dir->inode->archive, p, pdir, S_IFDIR | 0777);
- }
- }
- }
- /* Next iteration */
- *q = c;
- p = q + 1;
- q = strchr (p, '/');
- if (!q)
- q = strchr (p, 0);
- }
- if (pent == NULL)
- tarerrno = ENOENT;
- return pent;
- }
-
- static struct tarfs_entry *tarfs_find_entry (struct tarfs_entry *dir, char *name, int make_dirs)
- {
- struct tarfs_entry *res;
-
- errloop = 0;
- notadir = 0;
- res = __tarfs_find_entry (dir, name, NULL, make_dirs);
- if (res == NULL) {
- if (errloop)
- tarerrno = ELOOP;
- else if (notadir)
- tarerrno = ENOTDIR;
- }
- return res;
- }
-
- struct tar_pseudofile {
- struct tarfs_archive *archive;
- long pos;
- long begin;
- long end;
- struct tarfs_entry *entry;
- };
-
- static void *tar_open (char *file, int flags, int mode)
- {
- struct tar_pseudofile *tar_info;
- struct tarfs_archive *archive;
- char *p, *q;
- struct tarfs_entry *entry;
-
- if ((p = tarfs_get_path (file, &archive, 0, 0)) == NULL)
- return NULL;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- free (p);
- if (entry == NULL)
- return NULL;
- if ((entry = tarfs_resolve_symlinks (entry)) == NULL)
- return NULL;
- if (S_ISDIR (entry->inode->mode)) {
- tarerrno = EISDIR;
- return NULL;
- }
- if (flags & O_ACCMODE != O_RDONLY) {
- tarerrno = EROFS; /* At the moment we are RO */
- return NULL;
- }
-
- tar_info = (struct tar_pseudofile *) xmalloc (sizeof (struct tar_pseudofile), "Tar: tar_open");
- tar_info->archive = archive;
- tar_info->pos = 0;
- tar_info->begin = entry->inode->data_offset;
- tar_info->end = tar_info->begin + entry->inode->size;
- tar_info->entry = entry;
- entry->inode->is_open++;
-
- /* i.e. we had no open files and now we have one */
- vfs_rmstamp (&tarfs_vfs_ops, (vfsid) archive, 1);
- archive->fd_usage++;
- return tar_info;
- }
-
- static int tar_read (void *data, char *buffer, int count)
- {
- struct tar_pseudofile *file = (struct tar_pseudofile *)data;
-
- if (file->archive->is_gzipped != targz_growing &&
- mc_lseek (file->archive->fd, file->begin + file->pos, SEEK_SET) !=
- file->begin + file->pos) {
- tarerrno = EIO;
- return -1;
- }
-
- if (count > file->end - file->begin - file->pos)
- count = file->end - file->begin - file->pos;
- if (file->archive->is_gzipped == targz_growing) {
- char *p = buffer;
- int cnt = count;
- int i = file->begin + file->pos, j;
-
- if (i % RECORDSIZE) {
- j = RECORDSIZE - (i % RECORDSIZE);
- if (cnt < j)
- j = cnt;
- bcopy (((char *) find_current_record (file->archive, i / RECORDSIZE * RECORDSIZE)) + (i % RECORDSIZE),
- p, j);
- cnt -= j;
- p += j;
- i += j;
- }
- while (cnt) {
- if (cnt > RECORDSIZE)
- j = RECORDSIZE;
- else
- j = cnt;
- bcopy ((char *) find_current_record (file->archive, i),
- p, j);
- i += j;
- p += j;
- cnt -= j;
- }
- }
- else if ((count = mc_read (file->archive->fd, buffer, count)) == -1) {
- tarerrno = errno;
- return -1;
- }
- file->pos += count;
- return count;
- }
-
- static int tar_close (void *data)
- {
- struct tar_pseudofile *file;
-
- file = (struct tar_pseudofile *)data;
-
- file->archive->fd_usage--;
- if (!file->archive->fd_usage) {
- struct vfs_stamping *parent;
- vfs *v;
-
- v = vfs_type (file->archive->name);
- if (v == &local_vfs_ops) {
- parent = NULL;
- } else {
- parent = xmalloc (sizeof (struct vfs_stamping), "vfs stamping");
- parent->v = v;
- parent->id = (*v->getid) (file->archive->name, &(parent->parent));
- }
- vfs_add_noncurrent_stamps (&tarfs_vfs_ops, (vfsid) (file->archive), parent);
- }
- (file->entry->inode->is_open)--;
-
- free (data);
- return 0;
- }
-
- static int tar_errno (void)
- {
- return tarerrno;
- }
-
- static void *tar_opendir (char *dirname)
- {
- struct tarfs_archive *archive;
- char *p, *q;
- struct tarfs_entry *entry;
- struct tarfs_entry **tar_info;
-
- if ((p = tarfs_get_path (dirname, &archive, 1, 0)) == NULL)
- return NULL;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- free (p);
- if (entry == NULL)
- return NULL;
- if ((entry = tarfs_resolve_symlinks (entry)) == NULL)
- return NULL;
- if (!S_ISDIR (entry->inode->mode)) {
- tarerrno = ENOTDIR;
- return NULL;
- }
-
- tar_info = (struct tarfs_entry **) xmalloc (sizeof (struct tarfs_entry *), "Tar: tar_opendir");
- *tar_info = entry->inode->first_in_subdir;
-
- return tar_info;
- }
-
- static void *tar_readdir (void *data)
- {
- static struct {
- struct dirent dir;
- #ifdef NEED_EXTRA_DIRENT_BUFFER
- char extra_buffer [MC_MAXPATHLEN];
- #endif
- } dir;
-
- struct tarfs_entry **tar_info = (struct tarfs_entry **) data;
-
- if (*tar_info == NULL)
- return NULL;
-
- strcpy (&(dir.dir.d_name [0]), (*tar_info)->name);
-
- #ifndef DIRENT_LENGTH_COMPUTED
- dir.d_namlen = strlen (dir.dir.d_name);
- #endif
- *tar_info = (*tar_info)->next_in_dir;
-
- return (void *)&dir;
- }
-
- static int tar_closedir (void *data)
- {
- free (data);
- return 0;
- }
-
- static int _tar_stat (char *path, struct stat *buf, int resolve)
- {
- struct tarfs_archive *archive;
- char *p, *q;
- struct tarfs_entry *entry;
- struct tarfs_inode *inode;
-
- if ((p = tarfs_get_path (path, &archive, 0, 0)) == NULL)
- return -1;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- free (p);
- if (entry == NULL)
- return -1;
- if (resolve && (entry = tarfs_resolve_symlinks (entry)) == NULL)
- return -1;
- inode = entry->inode;
- buf->st_dev = inode->dev;
- buf->st_ino = inode->inode;
- buf->st_mode = inode->mode;
- buf->st_nlink = inode->nlink;
- buf->st_uid = inode->uid;
- buf->st_gid = inode->gid;
- #ifdef HAVE_ST_RDEV
- buf->st_rdev = inode->rdev;
- #endif
- buf->st_size = inode->size;
- #ifdef HAVE_ST_BLKSIZE
- buf->st_blksize = RECORDSIZE;
- #endif
- #ifdef HAVE_ST_BLOCKS
- buf->st_blocks = (inode->size + RECORDSIZE - 1) / RECORDSIZE;
- #endif
- buf->st_atime = inode->atime;
- buf->st_mtime = inode->mtime;
- buf->st_ctime = inode->ctime;
- return 0;
- }
-
- static int tar_stat (char *path, struct stat *buf)
- {
- return _tar_stat (path, buf, 1);
- }
-
- static int tar_lstat (char *path, struct stat *buf)
- {
- return _tar_stat (path, buf, 0);
- }
-
- static int tar_fstat (void *data, struct stat *buf)
- {
- struct tar_pseudofile *file = (struct tar_pseudofile *)data;
- struct tarfs_inode *inode;
-
- inode = file->entry->inode;
- buf->st_dev = inode->dev;
- buf->st_ino = inode->inode;
- buf->st_mode = inode->mode;
- buf->st_nlink = inode->nlink;
- buf->st_uid = inode->uid;
- buf->st_gid = inode->gid;
- buf->st_rdev = inode->rdev;
- buf->st_size = inode->size;
- #ifdef HAVE_ST_BLKSIZE
- buf->st_blksize = RECORDSIZE;
- #endif
- #ifdef HAVE_ST_BLOCKS
- buf->st_blocks = (inode->size + RECORDSIZE - 1) / RECORDSIZE;
- #endif
- buf->st_atime = inode->atime;
- buf->st_mtime = inode->mtime;
- buf->st_ctime = inode->ctime;
- return 0;
- }
-
- static int tar_chmod (char *path, int mode)
- {
- return chmod (path, mode);
- }
-
- static int tar_chown (char *path, int owner, int group)
- {
- return chown (path, owner, group);
- }
-
- static int tar_readlink (char *path, char *buf, int size)
- {
- struct tarfs_archive *archive;
- char *p, *q;
- int i;
- struct tarfs_entry *entry;
-
- if ((p = tarfs_get_path (path, &archive, 0, 0)) == NULL)
- return -1;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- free (p);
- if (entry == NULL)
- return -1;
- if (!S_ISLNK (entry->inode->mode)) {
- tarerrno = EINVAL;
- return -1;
- }
- if (size > (i = strlen (entry->inode->linkname))) {
- size = i;
- }
- strncpy (buf, entry->inode->linkname, i);
- return i;
- }
-
- static int tar_unlink (char *path)
- {
- return -1;
- }
-
- static int tar_symlink (char *n1, char *n2)
- {
- return -1;
- }
-
- static int tar_write (void *data, char *buf, int nbyte)
- {
- return -1;
- }
-
- static int tar_rename (char *a, char *b)
- {
- return -1;
- }
-
- static int tar_chdir (char *path)
- {
- struct tarfs_archive *archive;
- char *p, *q, *res;
- struct tarfs_entry *entry;
-
- tarerrno = ENOTDIR;
- if ((p = tarfs_get_path (path, &archive, 1, 0)) == NULL)
- return -1;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- if (entry == NULL) {
- free (p);
- return -1;
- }
- entry = tarfs_resolve_symlinks (entry);
- if (entry == NULL) {
- free (p);
- return -1;
- }
- if (!S_ISDIR (entry->inode->mode)) {
- free (p);
- return -1;
- }
- entry->inode->archive->current_dir = entry;
- res = copy_strings ("tar:", entry->inode->archive->name, p, NULL);
- free (p);
- tarerrno = 0;
- return 0;
- }
-
- static int tar_lseek (void *data, off_t offset, int whence)
- {
- struct tar_pseudofile *file = (struct tar_pseudofile *) data;
-
- switch (whence) {
- case SEEK_CUR:
- offset += file->pos; break;
- case SEEK_END:
- offset += file->end - file->begin; break;
- }
- if (offset < 0)
- file->pos = 0;
- else if (offset < file->end - file->begin)
- file->pos = offset;
- else
- file->pos = file->end;
- return file->pos;
- }
-
- static int tar_mknod (char *path, int mode, int dev)
- {
- return -1;
- }
-
- static int tar_link (char *p1, char *p2)
- {
- return -1;
- }
-
- static int tar_mkdir (char *path, mode_t mode)
- {
- return -1;
- }
-
- static int tar_rmdir (char *path)
- {
- return -1;
- }
-
- static vfsid tar_getid (char *path, struct vfs_stamping **parent)
- {
- struct tarfs_archive *archive;
- vfs *v;
- vfsid id;
- char *p;
- struct vfs_stamping *par;
-
- *parent = NULL;
- if ((p = tarfs_get_path (path, &archive, 0, 1)) == NULL) {
- return (vfsid) -1;
- }
- free (p);
- v = vfs_type (archive->name);
- id = (*v->getid) (archive->name, &par);
- if (id != (vfsid)-1) {
- *parent = xmalloc (sizeof (struct vfs_stamping), "vfs stamping");
- (*parent)->v = v;
- (*parent)->id = id;
- (*parent)->parent = par;
- (*parent)->next = NULL;
- }
- return (vfsid) archive;
- }
-
- static int tar_nothingisopen (vfsid id)
- {
- if (((struct tarfs_archive *)id)->fd_usage <= 0)
- return 1;
- else
- return 0;
- }
-
- static void free_entry (struct tarfs_entry *e)
- {
- int i = --(e->inode->nlink);
- if (S_ISDIR (e->inode->mode) && e->inode->first_in_subdir != NULL) {
- struct tarfs_entry *f = e->inode->first_in_subdir;
-
- e->inode->first_in_subdir = NULL;
- free_entry (f);
- }
- if (i <= 0) {
- if (e->inode->linkname != NULL)
- free (e->inode->linkname);
- if (e->inode->local_filename != NULL) {
- unlink (e->inode->local_filename);
- free (e->inode->local_filename);
- }
- free (e->inode);
- }
- if (e->next_in_dir != NULL)
- free_entry (e->next_in_dir);
- free (e->name);
- free (e);
- }
-
- static void tar_free (vfsid id)
- {
- struct tarfs_archive *parc;
- struct tarfs_archive *archive = (struct tarfs_archive *)id;
-
- free_entry (archive->root_entry);
- if (archive == first_archive) {
- first_archive = archive->next;
- } else {
- for (parc = first_archive; parc != NULL; parc = parc->next)
- if (parc->next == archive)
- break;
- if (parc != NULL)
- parc->next = archive->next;
- }
- free_archive (archive);
- }
-
- static char *tar_getlocalcopy (char *path)
- {
- struct tarfs_archive *archive;
- char *p, *q;
- struct tarfs_entry *entry;
-
- if ((p = tarfs_get_path (path, &archive, 1, 0)) == NULL)
- return NULL;
- q = (*p == '/') ? p + 1 : p;
- entry = tarfs_find_entry (archive->root_entry, q, 0);
- free (p);
- if (entry == NULL)
- return NULL;
- if ((entry = tarfs_resolve_symlinks (entry)) == NULL)
- return NULL;
-
- if (entry->inode->local_filename != NULL)
- return entry->inode->local_filename;
- p = mc_def_getlocalcopy (path);
- if (p != NULL) {
- entry->inode->local_filename = p;
- }
- return p;
- }
-
- static void tar_ungetlocalcopy (char *path, char *local, int has_changed)
- {
- /* We do just nothing. (We are read only and do not need to free local,
- since it will be freed when tar archive will be freed */
- }
-
- #ifdef HAVE_MMAP
- caddr_t tar_mmap (caddr_t addr, size_t len, int prot, int flags, void *data, off_t offset)
- {
- return (caddr_t)-1;
- }
-
- int tar_munmap (caddr_t addr, size_t len, void *data)
- {
- return -1;
- }
- #endif
-
- vfs tarfs_vfs_ops =
- {
- tar_open,
- tar_close,
- tar_read,
- tar_write,
-
- tar_opendir,
- tar_readdir,
- tar_closedir,
-
- tar_stat,
- tar_lstat,
- tar_fstat,
-
- tar_chmod,
- tar_chown,
-
- tar_readlink,
- tar_symlink,
- tar_link,
- tar_unlink,
-
- tar_rename,
- tar_chdir,
- tar_errno,
- tar_lseek,
- tar_mknod,
-
- tar_getid,
- tar_nothingisopen,
- tar_free,
-
- tar_getlocalcopy,
- tar_ungetlocalcopy,
-
- tar_mkdir,
- tar_rmdir,
- NULL,
- NULL
- #ifdef HAVE_MMAP
- , tar_mmap,
- tar_munmap
- #endif
- };
-